/**
 * Redis 存储服务
 *
 * 用途：
 * 1. 签到码存储（替代内存 Map）
 * 2. 会话缓存
 * 3. 其他临时数据存储
 */

import Redis from 'ioredis'

// Redis 配置
const REDIS_CONFIG = {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    password: process.env.REDIS_PASSWORD || undefined,
    db: parseInt(process.env.REDIS_DB || '0'),
    retryStrategy: (times: number) => {
        const delay = Math.min(times * 50, 2000)
        return delay
    }
}

// 简单的内存 Redis 替代实现（仅用于测试）
class InMemoryRedis {
    private store: Map<string, string> = new Map()
    private sets: Map<string, Set<string>> = new Map()

    on(_: string, __: any) {
        // noop for compatibility
    }

    status = 'ready'

    async setex(key: string, seconds: number, value: string) {
        this.store.set(key, value)
        // We don't implement TTL eviction; tests should not rely on precise timing except small checks
        return 'OK'
    }

    async set(key: string, value: string) {
        this.store.set(key, value)
        return 'OK'
    }

    async get(key: string) {
        return this.store.has(key) ? this.store.get(key)! : null
    }

    async del(...keys: string[]) {
        let removed = 0
        for (const k of keys) {
            if (this.store.delete(k)) removed++
            if (this.sets.delete(k)) removed++
        }
        return removed
    }

    async exists(key: string) {
        return this.store.has(key) || this.sets.has(key) ? 1 : 0
    }

    async sadd(key: string, member: string) {
        const set = this.sets.get(key) ?? new Set<string>()
        const before = set.size
        set.add(member)
        this.sets.set(key, set)
        return set.size - before
    }

    async srem(key: string, member: string) {
        const set = this.sets.get(key)
        if (!set) return 0
        const removed = set.delete(member)
        if (set.size === 0) this.sets.delete(key)
        return removed ? 1 : 0
    }

    async smembers(key: string) {
        const set = this.sets.get(key)
        return set ? Array.from(set) : []
    }

    async scard(key: string) {
        const set = this.sets.get(key)
        return set ? set.size : 0
    }

    pipeline() {
        const commands: Array<() => Promise<any>> = []
        return {
            get: (k: string) => {
                commands.push(async () => this.get(k))
                return this
            },
            exec: async () => {
                const results = []
                for (const fn of commands) {
                    try {
                        const r = await fn()
                        results.push([null, r])
                    } catch (e) {
                        results.push([e, null])
                    }
                }
                return results
            }
        }
    }

    async expire(_key: string, _seconds: number) {
        return 1
    }

    // minimal scan implementation: return all keys matching pattern 'checkin:codes:*'
    async scan(_cursor: string, _match: string, pattern: string, _countKey: string, _count: number) {
        // support only 'checkin:codes:*' pattern used in cleanupExpiredCodes
        const keys: string[] = []
        if (pattern === 'checkin:codes:*') {
            for (const k of this.sets.keys()) {
                if (k.startsWith('checkin:codes:')) keys.push(k)
            }
        }
        return ['0', keys]
    }
}

// 创建 Redis 客户端实例（单例模式）
let redisClient: any = null

/**
 * 获取 Redis 客户端实例（单例模式）
 */
export const getRedisClient = (): any => {
    if (!redisClient) {
        const useInMemory = process.env.NODE_ENV === 'test' || process.env.REDIS_IN_MEMORY === 'true'
        if (useInMemory) {
            redisClient = new InMemoryRedis()
            // no-op event handlers
        } else {
            redisClient = new Redis(REDIS_CONFIG)

            redisClient.on('connect', () => {
                console.log('✅ Redis connected successfully')
            })

            redisClient.on('error', (err: any) => {
                console.error('❌ Redis connection error:', err)
            })

            redisClient.on('close', () => {
                console.log('⚠️ Redis connection closed')
            })
        }
    }

    return redisClient
}

/**
 * 检查 Redis 连接状态
 */
export const isRedisConnected = (): boolean => {
    return redisClient?.status === 'ready'
}

/**
 * 关闭 Redis 连接
 */
export const closeRedisConnection = async (): Promise<void> => {
    if (redisClient) {
        await redisClient.quit()
        redisClient = null
    }
}

// ==================== Key 命名规则 ====================

/**
 * 签到码 Key 格式：checkin:code:{eventId}:{code}
 */
export const getSecretCodeKey = (eventId: string, code: string): string => {
    return `checkin:code:${eventId}:${code}`
}

/**
 * 活动的所有活跃签到码 Key：checkin:codes:{eventId}
 */
export const getEventCodesKey = (eventId: string): string => {
    return `checkin:codes:${eventId}`
}

/**
 * 用户会话 Key：session:{userId}
 */
export const getSessionKey = (userId: string): string => {
    return `session:${userId}`
}

/**
 * 统计缓存 Key：stats:{type}:{id}
 */
export const getStatsKey = (type: string, id: string): string => {
    return `stats:${type}:${id}`
}

// ==================== 签到码存储操作 ====================

export interface SecretCodeData {
    code: string
    eventId: string
    generatedBy: string
    expiresAt: string // ISO 时间字符串
}

/**
 * 存储签到码
 * @param eventId 活动ID
 * @param code 签到码
 * @param data 签到码数据
 * @param expiresIn 过期时间（秒）
 */
export const setSecretCode = async (
    eventId: string,
    code: string,
    data: SecretCodeData,
    expiresIn: number
): Promise<void> => {
    const client = getRedisClient()
    const key = getSecretCodeKey(eventId, code)

    // 存储签到码数据，设置过期时间
    await client.setex(key, expiresIn, JSON.stringify(data))

    // 将码添加到活动的码集合中
    const setKey = getEventCodesKey(eventId)
    await client.sadd(setKey, code)
    await client.expire(setKey, expiresIn + 60) // 集合的过期时间稍长一些
}

/**
 * 获取签到码
 * @param eventId 活动ID
 * @param code 签到码
 */
export const getSecretCode = async (eventId: string, code: string): Promise<SecretCodeData | null> => {
    const client = getRedisClient()
    const key = getSecretCodeKey(eventId, code)

    const data = await client.get(key)
    if (!data) {
        return null
    }

    return JSON.parse(data)
}

/**
 * 删除签到码
 * @param eventId 活动ID
 * @param code 签到码
 */
export const deleteSecretCode = async (eventId: string, code: string): Promise<void> => {
    const client = getRedisClient()
    const key = getSecretCodeKey(eventId, code)

    await client.del(key)

    // 从集合中移除
    const setKey = getEventCodesKey(eventId)
    await client.srem(setKey, code)
}

/**
 * 获取活动的所有活跃签到码
 * @param eventId 活动ID
 */
export const getActiveSecretCodes = async (eventId: string): Promise<SecretCodeData[]> => {
    const client = getRedisClient()
    const setKey = getEventCodesKey(eventId)

    // 获取所有码
    const codes = await client.smembers(setKey)

    // 批量获取码的详细信息
    const pipeline = client.pipeline()
    codes.forEach((code: string) => {
        pipeline.get(getSecretCodeKey(eventId, code))
    })

    const results = await pipeline.exec()
    if (!results) {
        return []
    }

    // 解析结果，过滤掉已过期的
    const activeCodes: SecretCodeData[] = []
    for (const [err, data] of results) {
        if (!err && data) {
            try {
                const codeData = JSON.parse(data as string)
                // 检查是否过期
                if (new Date(codeData.expiresAt) > new Date()) {
                    activeCodes.push(codeData)
                }
            } catch {
                // 忽略解析错误
            }
        }
    }

    return activeCodes
}

/**
 * 清理活动的所有签到码
 * @param eventId 活动ID
 */
export const clearEventSecretCodes = async (eventId: string): Promise<number> => {
    const client = getRedisClient()
    const setKey = getEventCodesKey(eventId)

    // 获取所有码
    const codes = await client.smembers(setKey)

    if (codes.length === 0) {
        return 0
    }

    // 批量删除
    const pipeline = client.pipeline()
    codes.forEach((code: string) => {
        pipeline.del(getSecretCodeKey(eventId, code))
    })
    pipeline.del(setKey)

    await pipeline.exec()

    return codes.length
}

/**
 * 清理所有过期的签到码（定时任务调用）
 */
export const cleanupExpiredCodes = async (): Promise<number> => {
    const client = getRedisClient()

    // 扫描所有签到码集合
    const pattern = 'checkin:codes:*'
    const keys: string[] = []

    // 使用 SCAN 命令遍历
    let cursor = '0'
    do {
        const [newCursor, matchedKeys] = await client.scan(cursor, 'MATCH', pattern, 'COUNT', 100)
        cursor = newCursor
        keys.push(...matchedKeys)
    } while (cursor !== '0')

    let cleanedCount = 0

    // 清理每个活动的过期码
    for (const setKey of keys) {
        const eventId = setKey.split(':')[2]
        if (!eventId) continue

        const codes = await client.smembers(setKey)

        for (const code of codes) {
            const codeKey = getSecretCodeKey(eventId, code)
            const exists = await client.exists(codeKey)

            // 如果码不存在（已过期），从集合中移除
            if (!exists) {
                await client.srem(setKey, code)
                cleanedCount++
            }
        }

        // 如果集合为空，删除集合
        const remainingCount = await client.scard(setKey)
        if (remainingCount === 0) {
            await client.del(setKey)
        }
    }

    return cleanedCount
}

// ==================== 会话存储操作 ====================

export interface SessionData {
    userId: string
    userType: 'admin' | 'member'
    organizationId?: string
    loginAt: string
}

/**
 * 存储用户会话
 * @param userId 用户ID
 * @param data 会话数据
 * @param expiresIn 过期时间（秒），默认7天
 */
export const setSession = async (
    userId: string,
    data: SessionData,
    expiresIn: number = 7 * 24 * 60 * 60
): Promise<void> => {
    const client = getRedisClient()
    const key = getSessionKey(userId)

    await client.setex(key, expiresIn, JSON.stringify(data))
}

/**
 * 获取用户会话
 * @param userId 用户ID
 */
export const getSession = async (userId: string): Promise<SessionData | null> => {
    const client = getRedisClient()
    const key = getSessionKey(userId)

    const data = await client.get(key)
    if (!data) {
        return null
    }

    return JSON.parse(data)
}

/**
 * 删除用户会话（登出）
 * @param userId 用户ID
 */
export const deleteSession = async (userId: string): Promise<void> => {
    const client = getRedisClient()
    const key = getSessionKey(userId)

    await client.del(key)
}

// ==================== 统计缓存操作 ====================

/**
 * 缓存统计数据
 * @param type 统计类型（如 'event', 'member', 'organization'）
 * @param id 实体ID
 * @param data 统计数据
 * @param expiresIn 过期时间（秒），默认1小时
 */
export const cacheStats = async (type: string, id: string, data: any, expiresIn: number = 3600): Promise<void> => {
    const client = getRedisClient()
    const key = getStatsKey(type, id)

    await client.setex(key, expiresIn, JSON.stringify(data))
}

/**
 * 获取缓存的统计数据
 * @param type 统计类型
 * @param id 实体ID
 */
export const getCachedStats = async (type: string, id: string): Promise<any | null> => {
    const client = getRedisClient()
    const key = getStatsKey(type, id)

    const data = await client.get(key)
    if (!data) {
        return null
    }

    return JSON.parse(data)
}

/**
 * 删除缓存的统计数据
 * @param type 统计类型
 * @param id 实体ID
 */
export const deleteCachedStats = async (type: string, id: string): Promise<void> => {
    const client = getRedisClient()
    const key = getStatsKey(type, id)

    await client.del(key)
}

/**
 * 批量删除统计缓存（使用模式匹配）
 * @param pattern 匹配模式（如 'stats:event:*'）
 */
export const deleteCachedStatsByPattern = async (pattern: string): Promise<number> => {
    const client = getRedisClient()
    const keys: string[] = []

    // 使用 SCAN 命令遍历
    let cursor = '0'
    do {
        const [newCursor, matchedKeys] = await client.scan(cursor, 'MATCH', pattern, 'COUNT', 100)
        cursor = newCursor
        keys.push(...matchedKeys)
    } while (cursor !== '0')

    if (keys.length === 0) {
        return 0
    }

    // 批量删除
    await client.del(...keys)

    return keys.length
}

// ==================== 通用 Key-Value 操作 ====================

/**
 * 设置键值对
 * @param key 键
 * @param value 值
 * @param expiresIn 过期时间（秒），可选
 */
export const set = async (key: string, value: string, expiresIn?: number): Promise<void> => {
    const client = getRedisClient()

    if (expiresIn) {
        await client.setex(key, expiresIn, value)
    } else {
        await client.set(key, value)
    }
}

/**
 * 获取键值
 * @param key 键
 */
export const get = async (key: string): Promise<string | null> => {
    const client = getRedisClient()
    return await client.get(key)
}

/**
 * 删除键
 * @param keys 键列表
 */
export const del = async (...keys: string[]): Promise<number> => {
    const client = getRedisClient()
    return await client.del(...keys)
}

/**
 * 检查键是否存在
 * @param key 键
 */
export const exists = async (key: string): Promise<boolean> => {
    const client = getRedisClient()
    const result = await client.exists(key)
    return result === 1
}

/**
 * 设置键的过期时间
 * @param key 键
 * @param seconds 秒数
 */
export const expire = async (key: string, seconds: number): Promise<boolean> => {
    const client = getRedisClient()
    const result = await client.expire(key, seconds)
    return result === 1
}

// 导出 Redis 服务
export const RedisService = {
    // 连接管理
    getRedisClient,
    isRedisConnected,
    closeRedisConnection,

    // 签到码操作
    setSecretCode,
    getSecretCode,
    deleteSecretCode,
    getActiveSecretCodes,
    clearEventSecretCodes,
    cleanupExpiredCodes,

    // 会话操作
    setSession,
    getSession,
    deleteSession,

    // 统计缓存
    cacheStats,
    getCachedStats,
    deleteCachedStats,
    deleteCachedStatsByPattern,

    // 通用操作
    set,
    get,
    del,
    exists,
    expire
}
